package com.eerl.pdev.ori.erl;

import com.eerl.pdev.ProjectContainer;
import com.eerl.pdev.util.EToolPluginConsoleToolWindowFactory;
import com.eerl.pdev.util.Notice;
import com.eerl.pdev.werl.MsgHandler;
import com.eerl.pdev.werl.impl.DefaultMsgHandler;
import com.ericsson.otp.erlang.*;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.project.Project;
import lombok.Getter;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class OriErlangNode extends Thread {

    public static final String CALL_RPC_FLAG = "call1730289556000call" ;
    private Project project;
    private final String cookie;
    private final String remoteHost;

    @Getter
    private final static String MAIL_BOX_NAME = "xllmailbox1729775080000xllmailbox";

    @Getter
    private String SELF_NODE_NAME;

    private final static String ERLANG_MFA_NODE_NAME = "rex";

    private final static int STATE_NONE = 0;
    private final static int STATE_RUNNING = 1;

    private OtpNode node;
    private int state = STATE_NONE;
    private OtpMbox mbox;
    private Map<String, MsgHandler> msgHandlerMap = new HashMap<>();
    private Map<Integer, Integer> nodeState = new HashMap<>();
    private MsgHandler defaultHandle;

    @Getter
    private long initTimeMs;

    // not allow too many request
    private BlockingQueue<OtpErlangObject> callReturnMsgQueue = new SynchronousQueue<>();
    private volatile boolean isCalling;

    private int callTimeoutMillion = 3000;

    public OriErlangNode(Project project, String cookie, String remoteHost) {
        this.project = project;
        this.cookie = cookie;
        this.remoteHost = remoteHost;
        initRemoteNodeConfiguration();
    }

    public void initRemoteNodeConfiguration() {
        try {
            SELF_NODE_NAME = "java_node" + System.currentTimeMillis() + "@127.0.0.1";
            EToolPluginConsoleToolWindowFactory.debugPrint(project, "gen self node name: " + SELF_NODE_NAME);
            EToolPluginConsoleToolWindowFactory.debugPrint(project, "connect ready, cookie: " + cookie);
            node = new OtpNode(SELF_NODE_NAME, cookie);
            mbox = node.createMbox(MAIL_BOX_NAME);
            defaultHandle = new DefaultMsgHandler(project);
            state = STATE_RUNNING;
            initTimeMs = System.currentTimeMillis();
            start();
        } catch (IOException e) {
            Notice.showNotification(project, "create erlang node fail, err: " + e.getMessage(), NotificationType.ERROR);
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.getLocalizedMessage());
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.fillInStackTrace().toString());
            EToolPluginConsoleToolWindowFactory.debugPrint(project, e.toString());
        }
    }

    private void handleMsg(String matchCase, OtpErlangTuple args) {
        // deal msg and update nodeState
        msgHandlerMap.getOrDefault(matchCase, defaultHandle).handle(nodeState, args);
    }

    @Override
    public void run() {
        while (state == STATE_RUNNING) {
            try {
                OtpErlangObject receive = mbox.receive();
                EToolPluginConsoleToolWindowFactory.debugPrint(project, receive.toString());
                if (receive instanceof OtpErlangTuple msg) {
                    OtpErlangObject matchCaseObj = msg.elementAt(0);
                    if (isCalling && CALL_RPC_FLAG.equals(matchCaseObj.toString())) {
                        // call msg
                        callReturnMsgQueue.put(msg);
                        continue;
                    } else if (ERLANG_MFA_NODE_NAME.equals(matchCaseObj.toString())) {
                        callReturnMsgQueue.put(msg.elementAt(1));
                        continue;
                    }
                    handleMsg(((OtpErlangString) matchCaseObj).stringValue(), msg);
                } else {
                    Notice.showNotification(project, "错误消息无法处理...." + receive, NotificationType.ERROR);
                    EToolPluginConsoleToolWindowFactory.debugPrint(project, receive.toString());
                }
            } catch (OtpErlangExit | OtpErlangDecodeException | InterruptedException e) {
                if (state == STATE_NONE) {
                    EToolPluginConsoleToolWindowFactory.printSimpleToConsole(project, "OriErlNode normal exit...");
                } else {
                    EToolPluginConsoleToolWindowFactory.errToConsole(project, "OriErlNode something err, exit...");
                    EToolPluginConsoleToolWindowFactory.errToConsole(project, e.getMessage());
                    EToolPluginConsoleToolWindowFactory.errToConsole(project, Arrays.toString(e.getStackTrace()));
                    EToolPluginConsoleToolWindowFactory.errToConsole(project, e.getLocalizedMessage());
                }
            }
        }
    }


    // ------------------------- public -----------------------------
    /**
     * add msg handler, if not add, will have one default handler to deal msg, it will print error msg direct into console
     */
    public void addMsgHandler(MsgHandler handler) {
        msgHandlerMap.put(handler.getMatchCase(), handler);
    }

    public void cast(String mod, String fun, OtpErlangList args) {
        if (state == STATE_NONE) {
            EToolPluginConsoleToolWindowFactory.errSimpleToConsole(project, "OriErlNode Not Start...");
        }
        // mbox will receive {rex, Data}
        mbox.send(ERLANG_MFA_NODE_NAME, remoteHost,
                new OtpErlangTuple(new OtpErlangObject[]{
                        mbox.self(),
                        new OtpErlangTuple(
                                new OtpErlangObject[] {
                                        new OtpErlangAtom("call"),
                                        new OtpErlangAtom(mod),
                                        new OtpErlangAtom(fun),
                                        args,
                                        new OtpErlangAtom("user")
                                }
                        )

                })
        );
    }

    /**
     * no realized
     */
    public OtpErlangTuple call(String mod, String fun, OtpErlangList args) {
        if (state == STATE_NONE) {
            EToolPluginConsoleToolWindowFactory.errSimpleToConsole(project, "OriErlNode Not Start...");
            return new OtpErlangTuple(new OtpErlangObject[]{
                    new OtpErlangString(""),
                    new OtpErlangAtom(true),
                    new OtpErlangString(""),
                    new OtpErlangString("OriNode not start"),
                    new OtpErlangString(""),
            });
        }
        isCalling = true;
        cast(mod, fun, args);
        try {
            OtpErlangObject msg = callReturnMsgQueue.poll(callTimeoutMillion, TimeUnit.MILLISECONDS);
            if (msg == null) {
                EToolPluginConsoleToolWindowFactory.errSimpleToConsole(project, "maby call timeout...");
                throw new RuntimeException("maby call timeout...");
            }
            if (msg instanceof OtpErlangTuple data) {
                return data;
            } else if (msg instanceof OtpErlangAtom data) {
                if ("ok".equals(msg.toString()) || "true".equals(msg.toString())) {
                    return new OtpErlangTuple(new OtpErlangObject[]{
                            new OtpErlangString(""),
                            new OtpErlangAtom(true),
                            new OtpErlangString(""),
                            new OtpErlangString(""),
                            data
                    });
                } else {
                    return new OtpErlangTuple(new OtpErlangObject[]{
                            new OtpErlangString(""),
                            new OtpErlangAtom(false),
                            new OtpErlangString(""),
                            data,
                            data
                    });
                }
            } else {
                return new OtpErlangTuple(new OtpErlangObject[]{
                        new OtpErlangString(""),
                        new OtpErlangAtom(false),
                        new OtpErlangString(""),
                        msg,
                        msg
                });
            }
        } catch (Exception e) {
            close();
            throw new RuntimeException("maby call timeout... or interrupted", e);
        } finally {
            isCalling = false;
        }
    }

    public void close() {
        ProjectContainer.getInstance(project).remove(this.getClass());
        mbox.close();
        node.close();
        state = STATE_NONE;
        if (!this.isInterrupted()) {
            this.interrupt();
        }
        EToolPluginConsoleToolWindowFactory.printSimpleToConsole(project, "OriErlNode ready to exit....");
    }
}
